#pragma once

#include "stdafx.h"

#include "PluginExport.h"
#include "NppWraper.h"

#include <vector>
#include <atlThread.h>
#include <Win32Helper.h>
using Win32Helper::CDirectory;

//////////////////////////////////////////////////////////////////////////
//
enum
{
	WM_USER_FILE_MODIFIED = (WM_USER+100),
	WM_USER_SEL_PREV,
	WM_USER_SEL_NEXT
};

//////////////////////////////////////////////////////////////////////////
//
//
class CFileWatcherThread :
	//public ATL::CThreadImpl<CFileWatcherThread, CThreadStopAtEvent>
	public ATL::CThreadImpl<CFileWatcherThread>
{
private:
	typedef ATL::CThreadImpl<CFileWatcherThread> _baseThreadImpl;

public:
	CFileWatcherThread( HWND hNotifyWindow,
		const CString& watchedDirPath, 
		const CString& watchedFileName) : m_hWatchedDir(NULL)
	{
		m_NotifyWindow = hNotifyWindow;
		ATL_CHECK_HWND(m_NotifyWindow);
		//
		m_WatchedDirPath = watchedDirPath;
		m_WatchedFileName = watchedFileName;
		ATLASSERT(CDirectory::IsDirectoryExist(m_WatchedDirPath));
		//
		m_ChangedEvent.Create(NULL,FALSE,FALSE);
		m_ShutdownEvent.Create(NULL,TRUE,FALSE);
		ATLASSERT(m_ChangedEvent.IsOpen() && m_ShutdownEvent.IsOpen());
	}

	~CFileWatcherThread()
	{
		//CloseWatchedDir();
	}

private:
	void OpenWatchedDir()
	{
		ATLASSERT(m_hWatchedDir.m_h==NULL);
		m_hWatchedDir.Attach( ::CreateFile(
			m_WatchedDirPath,
			FILE_LIST_DIRECTORY|GENERIC_READ, 
			FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 
			NULL,
			OPEN_EXISTING, 
			FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED , 
			0 )
			);
		ATLASSERT( m_hWatchedDir.m_h != INVALID_HANDLE_VALUE );
	}

	void CloseWatchedDir()
	{
		if (m_hWatchedDir)
		{
			m_hWatchedDir.Close();
		}
	}

	DWORD GetFileSize()
	{
		HANDLE hFile = ::CreateFile(
			m_WatchedDirPath+m_WatchedFileName,
			GENERIC_READ, 
			FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 
			NULL,
			OPEN_EXISTING, 
			NULL, 
			0 );
		ATLASSERT(hFile!=INVALID_HANDLE_VALUE);

		DWORD fileSize = ::GetFileSize(hFile,NULL);

		::CloseHandle(hFile);
		hFile = NULL;

		//
		return fileSize;
	}

public:
	// override
	void Abort()
	{
		m_ShutdownEvent.SetEvent();
		_baseThreadImpl::Abort();
	}

	// override
	DWORD Run()
	{
#if 0
		DWORD lastFileSize = 0;
		lastFileSize = GetFileSize();

		OpenWatchedDir();

		const int BUF_LEN = 65536;
		BYTE buffer[BUF_LEN] = { 0 };

		//DWORD filter = FILE_NOTIFY_CHANGE_FILE_NAME 
		//	| FILE_NOTIFY_CHANGE_DIR_NAME 
		//	| FILE_NOTIFY_CHANGE_SIZE 
		//	| FILE_NOTIFY_CHANGE_LAST_WRITE;
		DWORD filter = FILE_NOTIFY_CHANGE_LAST_WRITE 
			| FILE_NOTIFY_CHANGE_SIZE 
			| FILE_NOTIFY_CHANGE_LAST_ACCESS ;

		OVERLAPPED overlap;
		memset(&overlap,0,sizeof(overlap));
		overlap.hEvent = m_ChangedEvent;

		const int EVENT_COUNT = 2;
		HANDLE events[EVENT_COUNT];
		events[0] = m_ChangedEvent;
		events[1] = m_ShutdownEvent;
		//events[1] = m_hStopEvent;

		DWORD byteReturn = 0;
		TCHAR str_buf[MAX_PATH];
		while (!IsAborted())
		{
			BOOL suc = ::ReadDirectoryChangesW(m_hWatchedDir,buffer,BUF_LEN,TRUE,
				filter,&byteReturn,&overlap,NULL);
			if (!suc)
			{
				DWORD errcode = ::GetLastError();
				ATLASSERT(FALSE);
				break;
			}

			//DWORD waitRes = ::WaitForMultipleObjects(EVENT_COUNT,events,FALSE,INFINITE);
			//ATLASSERT( (waitRes==WAIT_OBJECT_0) || (waitRes==(WAIT_OBJECT_0+1)) );	

			const int WAIT_TIME = 1*1000;
			DWORD waitRes = ::WaitForMultipleObjects(EVENT_COUNT,events,FALSE,1000);
			if (waitRes==(WAIT_OBJECT_0+1))	// m_ShutdownEvent is signalled
				break;


			if (waitRes==WAIT_TIMEOUT)
			{
				DWORD filesize = GetFileSize();
				if (filesize>lastFileSize)
				{
					m_NotifyWindow.PostMessage(WM_USER_FILE_MODIFIED);
					lastFileSize = filesize;
				}

				continue;
			}

			ATLASSERT(waitRes==WAIT_OBJECT_0);

			//
			DWORD offset = 0, nextOffset = 0;
			FILE_NOTIFY_INFORMATION* pfni = (FILE_NOTIFY_INFORMATION*)(buffer+offset);
			offset += pfni->NextEntryOffset;
			do 
			{
				ATLASSERT(pfni->FileNameLength<MAX_PATH);
				SecureHelper::strncpy_x(str_buf,MAX_PATH,pfni->FileName,pfni->FileNameLength/2);
				str_buf[pfni->FileNameLength/2] = L'\0';
				CString filename = str_buf;
				//

				if (pfni->Action == FILE_ACTION_MODIFIED)
				{
					ATLTRACE2(atlTraceGeneral, 0, L"Modified file name : %s\n",filename);
					if (filename.CompareNoCase(L"abaqus.rpy")==0)
					{
						m_NotifyWindow.PostMessage(WM_USER_FILE_MODIFIED);
					}
				}
				//
				FILE_NOTIFY_INFORMATION* pfni = (FILE_NOTIFY_INFORMATION*)(buffer+offset);
				offset += pfni->NextEntryOffset;
			} while (pfni->NextEntryOffset != 0);
		}
#else

		ATL::CHandle hRpyFile;
		hRpyFile.Attach( ::CreateFile(
			m_WatchedDirPath+m_WatchedFileName,
			GENERIC_READ, 
			FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 
			NULL,
			OPEN_EXISTING, 
			NULL, 
			0 )
			);
		ATLASSERT(hRpyFile.m_h!=INVALID_HANDLE_VALUE);

		DWORD lastFileSize = ::GetFileSize(hRpyFile,NULL);

		while (!IsAborted())
		{
			const int WAIT_TIME = 1*1000;
			DWORD waitResult = ::WaitForSingleObject(m_ShutdownEvent,WAIT_TIME);
			if (waitResult==WAIT_OBJECT_0)	// shutdown event is signaled
			{
				break;
			}

			ATLASSERT(waitResult==WAIT_TIMEOUT);

			DWORD thisFileSize = ::GetFileSize(hRpyFile,NULL);
			if (thisFileSize!=lastFileSize)
			{
				m_NotifyWindow.PostMessage(WM_USER_FILE_MODIFIED);
				lastFileSize = thisFileSize;
			}
		}
#endif

		return 0;
	}

private:
	ATL::CWindow m_NotifyWindow;
	//
	ATL::CString m_WatchedDirPath;
	ATL::CString m_WatchedFileName;
	//
	ATL::CEvent m_ChangedEvent;
	ATL::CEvent m_ShutdownEvent;
	//
	ATL::CHandle m_hWatchedDir;
};


class CNotifyWindow :
	public ATL::CWindowImpl<CNotifyWindow>
{
private:
	typedef ATL::CWindowImpl<CNotifyWindow> _baseWinImpl;

public:
	BEGIN_MSG_MAP(CNotifyWindow)
		MESSAGE_HANDLER_EX(WM_USER_FILE_MODIFIED, OnFileModified)
		MESSAGE_HANDLER_EX(WM_USER_SEL_PREV, OnSelPrev)
		MESSAGE_HANDLER_EX(WM_USER_SEL_NEXT, OnSelNext)
	END_MSG_MAP()

public:
	CNotifyWindow() :
	m_DocIndex(-1), m_CurSel(0),m_pFileWatchThread(NULL),m_IsStopped(true)
	{ }

	~CNotifyWindow()
	{
		if (m_hWnd!=NULL && !IsStopped())
		{
			StopWatch();
			DestroyWindow();
		}
	}

public:
	HWND Create(const NppData& nppData,
		const CString& watchedDirPath, 
		const CString& watchedFileName)
	{
		ATLASSERT(m_hWnd==NULL);
		//
		m_NppData = nppData;
		ATL_CHECK_HWND(m_NppData._nppHandle);
		//
		m_WatchedDirPath = watchedDirPath;
		m_WatchedFileName = watchedFileName;
		ATLASSERT(CDirectory::IsDirectoryExist(m_WatchedDirPath));
		//
		CNppWindow npp(m_NppData);
		m_DocIndex = npp.GetCurrentDocIndex();
		//
		CSciWindow sci(m_NppData);
		int len = sci.GetTextLength();
		m_vecPos.push_back(len);
		//
		m_CurSel = m_vecPos.size()-1;
		//
		HWND hWnd = _baseWinImpl::Create(HWND_MESSAGE);
		//
		StartWatch();
		//
		return hWnd;
	}

	void Destroy()
	{
		if (m_hWnd!=NULL && !IsStopped())
		{
			StopWatch();
			_baseWinImpl::DestroyWindow();
		}
	}

public:
	void StartWatch()
	{
		ATLASSERT(m_pFileWatchThread==NULL);
		m_pFileWatchThread = new CFileWatcherThread(m_hWnd,m_WatchedDirPath,m_WatchedFileName);
		m_pFileWatchThread->SetDeleteOnExit(true);
		m_pFileWatchThread->Start();

		m_IsStopped = false;
	}

	void StopWatch()
	{
		if (m_hWnd!=NULL && m_pFileWatchThread!=NULL && !m_pFileWatchThread->IsAborted())
		{
			m_pFileWatchThread->Abort();
			m_pFileWatchThread = NULL;
		}
		m_IsStopped = true;
	}

	bool IsStopped() { return m_IsStopped; }

public:
	LRESULT OnFileModified(UINT uMsg, WPARAM wParam, LPARAM lParam)
	{
		CNppWindow npp(m_NppData);
		npp.ActiveDoc(m_DocIndex);
		npp.SendMessage(NPPM_MENUCOMMAND, 0, IDM_FILE_RELOAD);
		//
		CSciWindow sci(m_NppData);
		int vecSize = m_vecPos.size();
		int len = sci.GetTextLength();
		if (len > m_vecPos[vecSize-1])
		{
			m_vecPos.push_back(len);
			//
			vecSize = m_vecPos.size();
			sci.SetSel(m_vecPos[vecSize-2],m_vecPos[vecSize-1]);
			//
			m_CurSel = m_vecPos.size()-1;
		}

		//
		return 0;
	}

	LRESULT OnSelPrev(UINT uMsg, WPARAM wParam, LPARAM lParam)
	{
		ATLASSERT( 0<=m_CurSel && m_CurSel<=(m_vecPos.size()-1) );
		if (m_CurSel>0)
		{
			--m_CurSel;
		}

		CSciWindow sci(m_NppData);
		if (m_CurSel<=0)
		{
			sci.SetSel(0,m_vecPos[m_CurSel]);
		}
		else
		{
			sci.SetSel(m_vecPos[m_CurSel-1],m_vecPos[m_CurSel]);
		}

		return 0;
	}

	LRESULT OnSelNext(UINT uMsg, WPARAM wParam, LPARAM lParam)
	{
		ATLASSERT( 0<=m_CurSel && m_CurSel<=(m_vecPos.size()-1) );
		if (m_CurSel<(m_vecPos.size()-1))
		{
			++m_CurSel;
		}

		CSciWindow sci(m_NppData);
		if (m_CurSel<=0)
		{
			sci.SetSel(0,m_vecPos[m_CurSel]);
		}
		else
		{
			sci.SetSel(m_vecPos[m_CurSel-1],m_vecPos[m_CurSel]);
		}

		return 0;
	}

private:
	NppData m_NppData;
	//
	int m_DocIndex;
	int m_CurSel;
	//
	std::vector<int> m_vecPos;
	//
	ATL::CString m_WatchedDirPath;
	ATL::CString m_WatchedFileName;
	//
	CFileWatcherThread* m_pFileWatchThread;
	//
	bool m_IsStopped;
};

